package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author atguigu-mqx
 * @ClassName TsLoginAspect
 * @description: TODO
 * @date 2024年12月28日
 * @version: 1.0
 */
@Component
@Aspect
public class TsLoginAspect {


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 环绕通知
     * @param point
     * @param tsLogin
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(tsLogin)")
    public Object doConcurrentOperation(ProceedingJoinPoint point, TsLogin tsLogin) throws Throwable {
        //  有当前注解必须要登录，并且还需要将userId 存储到本地线程！
        //  如何判断当前这个用户是否登录? 1. 登录成功; a. 返回token存储到请求头中; b. 将用户信息存储到redis中！
        //  切面类中将用户Id存储到本地线程
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)previousAttributes;
        //  获取请求对象
        HttpServletRequest request = requestAttributes.getRequest();
        //  获取响应对象
        //  HttpServletResponse response = requestAttributes.getResponse();
        String token = request.getHeader("token");
        //  判断注解属性
        if(tsLogin.required()){
            // 必须要登录
            if (StringUtils.isEmpty(token)){
                //  说明未登录;
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
            } else {
                //  去缓存中获取用户信息; 缓存中是如何存储用户信息的? redis: 考虑数据类型
                //  key 必须唯一！
                //  set key=token value={userInfo}; get key  hset key=token field=name value=zhangsan;
                String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX+token;
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(loginKey);
                //  判断
                if (null == userInfo){
                    throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
                }
            }
        }
        try {
            //  判断 token 是否未空
            if (!StringUtils.isEmpty(token)){
                //  set key=token value={userInfo}; get key  hset key=token field=name value=zhangsan;
                String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX+token;
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(loginKey);
                //  判断用户信息不为空，则将用户Id 存储到本地线程
                if (null != userInfo) {
                    //  ThreadLocal 注意内存泄漏！
                    AuthContextHolder.setUserId(userInfo.getId());
                }
            }
            //  执行方法体
            return point.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            AuthContextHolder.removeUserId();
        }
    }
}
